-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Minimize HardwareSerial Receive and Transmit delays #3664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@dflogeras have you looked at the bytes that it claims it have received? |
@dflogeras also if you can play with |
@dflogeras PS Are you able to do local patches to esp-hal-uart.c to verify solutions ? |
I am running it at 460800 baud during that phase. I can certainly patch esp-hal-uart.c if it is built as part of the Arduino build process. I am working on another job today, but will get back to this tomorrow and can try tx_idle_num changes. I didn't diagnose down to the byte yet, for I will need to change my test setup for triggering on when it happens, which I can't do until at least tomorrow. |
@dflogeras
And see if there is different behavior ? @me-no-dev : |
Patching esp32-hal-uart.c as above: now instead of uartRead() returning early with incorrect data, now it always times out with a short count (even though I can see the sensor is sending 1024 bytes on the logic analyzer). Seemed to be always in the 700s, but not always the same number of bytes. Trying values 1 and 10 for tx_idle_num both still exhibited the original behaviour (returning a full read, but clearly early and with incorrect data). |
@dflogeras First observation : The first byte being wrong in a message is always nr 256. |
@dflogeras
|
I will check when I am home later in the day. In the meantime, can you verify that the problem goes away for you by reverting this commit? |
Ok, will verify and report. Probably tomorrow. |
OK. Your last patch on top of this commit had the same behaviour as your previous patch: timeouts after the default 1s with short byte count (in the 730-750 bytes range when I asked for 1024) Your previous comment of needing to go to 800kBaud to reproduce made me think of something else though. I forgot to mention, that I am running my CPU at 80MHz (for power consumption). If I switch to 160 or 240, I get the full data (both this commit unpatched, as well as with your latest patch). What I don't know is what that means. Is running the CPU faster just hiding a race condition? I am hard pressed to believe that a 80MHz CPU cannot keep up with a 400kBaud (45 kBytes/s), while doing almost nothing else. |
@dflogeras Good to mention.
Add check in isr
And i see the GPIO 27 toggle -> loosing chars because of fifo overflow. Your situation now with readbyte() timeout is explained by this. |
Ok good we're on the same page now. So, the hardware literally cannot keep up @ 80MHz? |
Also, to put it another way, why was it not losing characters prior to this commit? |
It's the software which can't keep up at 80Mhz, that should empty the fifo. |
Sorry, yes that's what I mean, the hardware being the CPU @ 80Mhz That still doesn't explain why it was (seemingly) working prior to this. |
Will check on that in the coming days. |
@hreintke @dflogeras you need a minimum number of cpu cycle to service an interrupt. With the I2C subsystem, I adjust the FIFO thresholds inversely with cpu clock and limit maximum I2C bus clock at lower cpu speeds. It seems to work at 800k Baud and 112 as Fifo Full Threshold, with Fifo Threshold at 112 that leaves 16 byte periods to start emptying the fifo before overflows happen. That calculates to 200us with a 80MHz clock that is 16k clock cycles. The interrupt would trigger about every 1.4ms. It looks like that is not enough. Try reducing the Fifo threshold inversely with cpu clock and Baud, If 112 works at 240mhz and 800KHz and you divide cpu by 3, divide threshold by 3, So, 112 becomes 38. It still uses the full Fifo, but it gives you additional time to react. Chuck. |
@stickbreaker I agree with your assessment of reducing the 'high water alarm' on the fifo. However in practice it didn't fix the problem I am seeing. I reduced the threshold to 32 (in addition to @hreintke last patch from the above comment), and it received even less short-count data after timing out. |
@dflogeras : Just to be sure : Are you emptying the uart buffers with Edit : test result I attached master and slave test programs. |
@stickbreaker @me-no-dev
The detail :
has a check on full queue before sending to queue.
|
static void IRAM_ATTR _uart_isr(void *arg)
{
uint8_t i, c;
BaseType_t xHigherPriorityTaskWoken = FALSE;
uart_t* uart;
for(i=0;i<3;i++){
uart = &_uart_bus_array[i];
if(uart->intr_handle == NULL){
continue;
}
uart->dev->int_clr.rxfifo_full = 1;
uart->dev->int_clr.frm_err = 1;
uart->dev->int_clr.rxfifo_tout = 1;
if (i==1) digitalWrite(26,HIGH);
while(uart->dev->status.rxfifo_cnt || (uart->dev->mem_rx_status.wr_addr != uart->dev->mem_rx_status.rd_addr)) {
c = uart->dev->fifo.rw_byte;
if(uart->queue != NULL ) {
xQueueSendFromISR(uart->queue, &c, &xHigherPriorityTaskWoken);
}
}
if (i==1) digitalWrite(26,LOW);
}
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
} I don't see any problem with your suggested changes. My only question relates back to the choice of servicing all three UARTs whenever one of them triggers an interrupt. The Remember to remove your debug ( |
I just had a stupid idea, the current code drops characters when the queue becomes full. What do you think about a condition to stop emptying the fifo if the fifo is lower than the interrupt trigger and the queue is full, allow foreground a chance to process and save the chars in the fifo? This code is UNTESTED and Just off-the-cuff, to BBW(Buyer Be Ware!) static void IRAM_ATTR _uart_isr(void *arg)
{
uint8_t i, c;
BaseType_t xHigherPriorityTaskWoken = FALSE;
uart_t* uart;
for(i=0;i<3;i++){
uart = &_uart_bus_array[i];
if(uart->intr_handle == NULL){
continue;
}
// adjust rxfifo empty point based on latency between interrupt triggering and ISR firing
// minimum free space of 8 characters
if (uart->dev->int_st.rxfifo_full ) { //only adjust if this UART triggered fifoFull
if( uart->dev->status.rxfifo_cnt > uart->dev->conf1.rxfifo_full_thrd){ // delayed service
if( (128 - uart->dev->status.rxfifo_cnt ) < 8 ) { // almost overflowed Fifo
if (uart->dev->conf1.rxfifo_full_thrhd > 32 ) { // min fifo level
uart->dev->conf1.rxfifo_full_thrhd -= 1; // trigger earlier
}
}
}
else if ( uart->dev->conf1.rxfifo_full_thrd <120){// under utilizing Fifo
uart->dev->confg1.rxfifo_full_thrd += 1;
}
}
// end of fifo trigger adjust
uart->dev->int_clr.rxfifo_full = 1;
uart->dev->int_clr.frm_err = 1;
uart->dev->int_clr.rxfifo_tout = 1;
if (i==1) digitalWrite(26,HIGH);
while(uart->dev->status.rxfifo_cnt || (uart->dev->mem_rx_status.wr_addr != uart->dev->mem_rx_status.rd_addr)) {
c = uart->dev->fifo.rw_byte;
if(uart->queue != NULL ) {
if ( !xQueueSendFromISR(uart->queue, &c, &xHigherPriorityTaskWoken)){
// queue is full, exit if fifo level is below fifo trigger
if( uart->dev->status.rxfifo_cnt < uart->dev->conf1.rxfifo_full_thrhd )
continue;
}
}
}
if (i==1) digitalWrite(26,LOW);
}
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
} @dflogeras might be seeing the character loss because the queue is overflowing, not because the fifo is overflowing. If the interrupt is triggering more often then his fore ground process does not have a chance to process before the Chuck. |
@stickbreaker |
You are correct. The only way not to loose that char would be to check for QueueFull before attempting the add. But, based on your results. That check before call is expensive in time.
One way around this would be to store the queue size in //cut
uart->dev->int_clr.frm_err = 1;
uart->dev->int_clr.rxfifo_tout = 1;
if (i==1) digitalWrite(26,HIGH);
size_t freeCount = 0;
if(uart->queue ) {
freeCount = uart->queueSize - uxQueueMessagesWaitingFromISR(uart->queue );
}
while(uart->dev->status.rxfifo_cnt || (uart->dev->mem_rx_status.wr_addr != uart->dev->mem_rx_status.rd_addr)) {
if(freeCount-- > 0) {
c = uart->dev->fifo.rw_byte;
xQueueSendFromISR(uart->queue, &c, &xHigherPriorityTaskWoken);
)
else {
// queue is full, exit if fifo level is below fifo trigger
if( uart->dev->status.rxfifo_cnt < uart->dev->conf1.rxfifo_full_thrhd ){
break; // stop emptying fifo, Queue is full, but fifo has room
}
else { // empty one char from FIFO and discard
c = uart->dev->fifo.rw_byte;
}
}
}
if (i==1) digitalWrite(26,LOW);
} Oops, used For this code to work, the Chuck. |
This is better inner loop code:' while(uart->dev->status.rxfifo_cnt || (uart->dev->mem_rx_status.wr_addr != uart->dev->mem_rx_status.rd_addr)) {
c = uart->dev->fifo.rw_byte;
if(freeCount-- > 0) {
xQueueSendFromISR(uart->queue, &c, &xHigherPriorityTaskWoken);
}
else {
// queue is full, exit if fifo level is below fifo trigger
if( uart->dev->status.rxfifo_cnt < uart->dev->conf1.rxfifo_full_thrhd ) {
break; // stop emptying fifo, Queue is full, but fifo has room
}
}
}
if (i==1) digitalWrite(26,LOW); Always have to empty at least one char from fifo. So, it will only loose the char if rxFifo_Full interrupt triggered and the Queue is already full. Chuck. |
I can confirm that just by removing the xQueueIsQueueFullFromISR from the isr code, I can now keep my head above water @ 80MHz. |
@stickbreaker What also might help is moving to Ringbuffer |
@dflogeras Have you verified where the loss of data was happening in your case? Was the loss because of rxfifo overflow or because the Queue was full? Chuck. |
@hreintke saving the "extra" character inside the Another possible problem with leaving the data in the rxfifo is when does All of my coding is untested theoretical. So, there are probably many hidden pitfalls. Chuck. |
Submitted PR #3713 with bugfix and isr improvement. @stickbreaker
My suggestion would be to leave it as is. On fifo size. What is your opinion ? |
It should change as part of the CPU frequency callback which adjusts the baud rate parameters as well. It should not be adjusted inside the ISR. |
@hreintke @atanisoft I agree with both of you, my code would add complexity and uncertainty. Neither of which is good in a foundational driver. @hreintke maybe have some persistent flag "queueOverfow", or a @atanisoft any idea on a formula to generate the Chuck. |
solved #2004 |
* master: M5Stack's product offering includes various ESP32-based camera devices. (espressif#4030) Fix for issue 3974 m_connectedCount incorrectly decremented when no connection exists Add a new board of KITS for IoT education (espressif#3703) update M5Camera pins (espressif#4021) Update SD_MMC.cpp (espressif#4020) Added missing wifi_provisioning dependency. (espressif#4003) HardwareSerial bugfix & improvement (espressif#3713) Allow using custom linker scripts (espressif#3735) Add M5Stack-ATOM Board (espressif#3883) Minor modifications in provisioning (espressif#3919) Add support of unified provisioning to Arduino Update install-platformio-esp32.sh add new board Handbit (espressif#3807) Move _STREAM_BOUNDARY before _STREAM_PART (espressif#3720) Add Senses's WEIZEN board from Senses IoT platform (espressif#3687) Revert "std::shared_ptr Memory Leak (espressif#3680)" (espressif#3682) std::shared_ptr Memory Leak (espressif#3680) Minimize HardwareSerial Receive and Transmit delays (espressif#3664) fix removeApbChangeCallback() error in spiStopBus() (espressif#3675) # Conflicts: # CMakeLists.txt
In current HardwareSerial/esp-uart-hal
In the uart configutarion setting there is a parameter tx_idle_num.
tx_idle_num is : idle interval after tx FIFO is empty(unit: the time it takes to send one bit under current baudrate)
This tx_idle_num is set to 256.
Resulting in a forced transmit idle time after txFifo empty to next.
This PR sets the tx_idle_num value to zero at uartBegin().
Result is an immediate start of transmission when a char is send.
The chars are first received in rxFifo buffer which is transfered to HardwareSerial Queue on either fifo filled with 112 chars or 2 bytes idle time.
Resulting in a delay at application level processing the incoming messages.
This PR takes the characters in rxFifo into account when calling available(), read() and peek().
Result is an immediate transfer from queue and/or fifo when application requests data.
To demonstrate/test I have an application doing message transfer at 19200 baud
Running with Master : duration 19.3 ms
Running with PR : duration 9.3 ms